﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using MugenInjection.Activators;
using MugenInjection.Delegates;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Scope;
#if !SILVERLIGHT
#if !NETFX_CORE
using MugenInjection.Xml.Properties;
#endif
#endif


namespace MugenInjection.Xml.Infrastructure
{
    internal static class XmlLoaderUtils
    {
        #region Const

        public const string BindingsNamespace = "urn:xmlbindings-shema";
        public const string BindingsElementName = "Bindings";
        public const string BindingElementName = "Binding";
        public const string BindingNameAttr = "name";

        public const string BindingTypeAttr = "type";
        public const string BindingToAttr = "to";
        public const string BindingScopeAttr = "scope";
        public const string BindingNamedAttr = "named";
        public const string BindingPriorityAttr = "priority";
        public const string BindingTryDisposeAttr = "tryDisposeObjects";
        public const string BindingActivatorAttr = "activator";
        public const string BindingConstructorResolverAttr = "constructorResolver";
        public const string BindingWhenTypeAttr = "whenType";
        public const string BindingWhenValueAttr = "whenValue";

        public const string FromElementName = "From";
        public const string FromTypeElementName = "Type";
        public const string TypeValueAttr = "value";

        public const string ParametersElementName = "Parameters";
        public const string ParameterElementName = "Parameter";
        public const string ParameterTypeAttr = "type";
        public const string ParameterMethodNameAttr = "methodName";
        public const string ParameterNameAttr = "name";
        public const string ParameterValueAttr = "value";

        public const string SettingsElementName = "Settings";
        public const string SettingElementName = "Setting";
        public const string SettingKeyAttr = "key";
        public const string SettingValueAttr = "value";

        public const string SessionScopeProvider = "_SessionScope_";
        public const string HttpRequestScopeProvider = "_HttpRequestScope_";
        public const string OperationRequestScopeProvider = "_OperationRequest_";

        #endregion

        #region Fields

        private static readonly CacheCollection<string, Func<IScopeLifecycle>> CacheScopes;

        private static readonly CacheCollection<string, Func<IActivator>> CacheActivators;

        private static readonly CacheCollection<string, Func<IConstructorResolver>> CacheConstrResolvers;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlLoaderUtils"/> class.
        /// </summary>
        static XmlLoaderUtils()
        {
            CacheScopes = new CacheCollection<string, Func<IScopeLifecycle>>();
            CacheScopes.Add("SingletonScopeLifecycle", () => new SingletonScopeLifecycle());
#if !NETFX_CORE
            CacheScopes.Add("ThreadScopeLifecycle", () => new ThreadScopeLifecycle());
#endif
            CacheScopes.Add("TransientScopeLifecycle", () => new TransientScopeLifecycle());
            CacheScopes.Add("ManagedScopeLifecycle", () => new ManagedScopeLifecycle());
            CacheScopes.Add("UnitOfWorkScopeLifecycle", () => new UnitOfWorkScopeLifecycle());
            CacheScopes.Add("HttpRequestScopeLifecycle", () => GetWebScope(HttpRequestScopeProvider));
            CacheScopes.Add("OperationRequestScopeLifecycle", () => GetWebScope(OperationRequestScopeProvider));
            CacheScopes.Add("SessionScopeLifecycle", () => GetWebScope(SessionScopeProvider));

            CacheActivators = new CacheCollection<string, Func<IActivator>>();
#if !NETFX_CORE
            CacheActivators.Add("EmitActivator", () => new EmitActivator());
#endif
            CacheActivators.Add("ReflectionActivator", () => new ReflectionActivator());

            CacheConstrResolvers = new CacheCollection<string, Func<IConstructorResolver>>();
            CacheConstrResolvers.Add("DefaultConstructorResolver", () => new DefaultConstructorResolver());
        }

        #endregion

        #region Methods

        public static T ParseEnum<T>(string enumType) where T : struct
        {
            return (T)Enum.Parse(typeof(T), enumType, true);
        }

        public static Type ParseType(string type)
        {
#if NETFX_CORE
            return Type.GetType(type, true);
#else
            return Type.GetType(type, true, true);
#endif
        }

        public static CanResolveBindingDelegate GetCanResolveMethod(string methodName)
        {
            var methodInfo = ParseMethod(methodName, typeof(bool));
            return context => (bool)methodInfo.Invoke(null, new object[] { context });
        }

        public static MethodBindingDelegate GetToMethodValue(string methodName)
        {
            var methodInfo = ParseMethod(methodName);
            return context => methodInfo.Invoke(null, new object[] { context });
        }

        public static Func<IScopeLifecycle> GetScope(IInjector injector, string scope)
        {
            return GetFromCache(scope, CacheScopes, injector.Settings.DefaultScopeLifecycleFactory);
        }

        public static Func<IActivator> GetActivator(string activator, Func<IActivator> defaultValue)
        {
            return GetFromCache(activator, CacheActivators, defaultValue);
        }

        public static Func<IConstructorResolver> GetConstructorResolver(IInjector injector, string constrResolver)
        {
            return GetFromCache(constrResolver, CacheConstrResolvers, injector.Settings.DefaultConstructorResolverFactory);
        }

        public static BindingPriority? GetPriority(string priority)
        {
            if (string.IsNullOrEmpty(priority))
                return null;
            return ParseEnum<BindingPriority>(priority);
        }

        public static bool GetTryDisposeObject(string tryDispose)
        {
            if (string.IsNullOrEmpty(tryDispose))
                return false;
            return bool.Parse(tryDispose);
        }

        public static void VerifyXmlFile(string xml)
        {
#if !SILVERLIGHT
#if !NETFX_CORE
#if !WINDOWS_PHONE
            // Declare local objects
            var rs = new XmlReaderSettings { ValidationType = ValidationType.Schema };
            rs.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation |
                                  XmlSchemaValidationFlags.ReportValidationWarnings;
            rs.ValidationEventHandler += (sender, args) =>
                                             {
                                                 if (args.Severity == XmlSeverityType.Error)
                                                     throw args.Exception;
                                             };
            rs.Schemas.Add(null, XmlReader.Create(new StringReader(ShemaResource.ValidationShema)));

            using (XmlReader xmlValidatingReader = XmlReader.Create(new StringReader(xml), rs))
            {
                while (xmlValidatingReader.Read())
                {
                }
            }
#endif
#endif
#endif
        }

        private static MethodInfo ParseMethod(string methodValue, Type returnType = null)
        {
            string[] strings = methodValue.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
            if (strings.Length != 2)
                throw new ArgumentException(string.Format("Value {0} is not valid for method binding.", methodValue));
            Type type = ParseType(strings[0]);
            MethodInfo methodInfo = type.GetMethod(strings[1]);
            if (methodInfo == null)
                throw new ArgumentException(string.Format("Cannot find method {0} in type {1}", strings[1], type));
            if (!methodInfo.IsStatic)
                throw new ArgumentException(string.Format("Cannot use method {0} in type {1}, because is not a static.", strings[1], type));
            ParameterInfo[] parameterInfos = methodInfo.GetParameters();
            if (parameterInfos.Length != 1 && parameterInfos[0].ParameterType != typeof(IBindingContext) &&
                (returnType != null && returnType != methodInfo.ReturnType))
                throw new ArgumentException(string.Format("Cannot use method {0} in type {1}, because method parameters must be a SomeMethod(IBindingContext)", strings[1], type));
            return methodInfo;
        }

        private static IScopeLifecycle GetWebScope(string name)
        {
            return (IScopeLifecycle)ParseType("MugenInjection.Scope.WebContextScopeLifecycle")
                                            .GetConstructor(new[] { typeof(string) })
                                            .Invoke(new object[] { name });
        }

        private static Func<T> GetFromCache<T>(string value, CacheCollection<string, Func<T>> cache, Func<T> defaultValue)
        {
            if (string.IsNullOrEmpty(value))
                return defaultValue;
            Func<T> func = cache[value];
            if (func != null)
                return func;
            var type = ParseType(value);
            if (type != null)
            {
                Validate.IsAssignable(typeof(T), type);
                return () => (T)Activator.CreateInstance(type);
            }
            throw new InvalidSettingException(string.Format("Cannot find value with key {0}", value));

        }

        #endregion

    }
}
